package com.base.game.terrain;

import java.util.ArrayList;
import java.util.Random;

import com.base.engine.graphics.TexUV;
import com.base.engine.graphics.Vertex3f;
import com.base.engine.system.* ;
import com.base.game.graphics.TextureManager;

import static org.lwjgl.opengl.GL11.*;

public class Chunk {

	public static final int SIZE_X = 16, SIZE_Y = 256, SIZE_Z = SIZE_X;
	public static final float VOXEL_SIZE = 5.0f;

	static int heightMultFormula(float y){
		return (int) ((y / 2.0f) * SIZE_Y * 2) ; 
	}
		
	final Random random = new Random();

	private boolean loaded;
	private int list;

	private ID_2D id;
	private float[][] heightValues;
	private EBlockType[][][] blocks;
	private Vertex3f renderingOffset;
	
	/* Neighbours:
	 * 
	 * 0 -> North
	 * 1 -> South 
	 * 2 -> West
	 * 3 -> East
	 */
	private Chunk[] neighbours = new Chunk[4]; 

	public ID_2D getID() {
		return this.id;
	}

	float[][] getHeightValues() {
		return this.heightValues;
	}

	public EBlockType getBlockByID(ID_3D id){
		return this.blocks[id.getX()%SIZE_X][id.getY()%SIZE_Y][id.getZ()%SIZE_Z];
	}
	
	public Chunk(){
		this.loaded = false;
		this.blocks = new EBlockType[SIZE_X][SIZE_Y][SIZE_Z];
		this.renderingOffset = new Vertex3f(0, 0, 0);
	}
	
	public Chunk(ID_2D id) {
		this(); 
		this.id = id;
	}

	void setID(ID_2D id	){
		this.id = id ; 
	}
	
	void applyHeightValues(float[][] heights) {
		if (heights != null && heights.length == SIZE_X
				&& heights[0].length == SIZE_Z) {
			this.heightValues = heights;
			int height = 0;
			for (int x = 0; x < SIZE_X; x++)
				for (int z = 0; z < SIZE_Z; z++) {
					height = getHeightValue(this, x, z);
					for (int y = 0; y < SIZE_Y; y++) {
						if (y <= height)
							blocks[x][y][z] = EBlockType.DIRT;
						else
							blocks[x][y][z] = EBlockType.AIR;
					}

				}
		}
	}
	
	private static int getHeightValue(Chunk c, int x, int z) {
		float y = 0.0f ; 
		if(x<0){
			x = SIZE_X-1 ;
			c = c.getNeighbour(EDirection.SOUTH); 
		}
		else if(x>SIZE_X-1){
			x = 0 ; 
			c = c.getNeighbour(EDirection.NORTH); 
		}
		if(z<0){
			z = SIZE_Z-1 ; 
			c = c.getNeighbour(EDirection.WEST); 
		}
		else if(z>SIZE_Z-1){
			z = 0; 
			c = c.getNeighbour(EDirection.EAST); 
		}
		y = c.heightValues[x][z]; 
		return  heightMultFormula(y);
	}
	
	private int getNeighbourChunkIndex(EDirection dir){
		int index = 0; 
		switch(dir){
		case NORTH: index = 0 ; break ;
		case SOUTH: index = 1 ; break ; 
		case WEST: index = 2 ; break ; 
		case EAST: index = 3 ; break ; 
		}
		return index ;
	}
	private void setNeibouredChunk(int index, Chunk c){
		this.neighbours[index] = c ; 
	}
	void setNeighbourChunk(EDirection dir, Chunk c){
		
		this.setNeibouredChunk(this.getNeighbourChunkIndex(dir), c); 
	}
	Chunk getNeighbour(EDirection dir){
		return this.neighbours[this.getNeighbourChunkIndex(dir)]; 
	}
	
	boolean isHighestValue(int x, int z, int heightToCheck){
		return heightToCheck == getHeightValue(this, x, z); 
	}

	public EBlockType getSouthernBlock(int x, int y, int z){
		return (x>0) ? blocks[x-1][y][z] : this.getNeighbour(EDirection.SOUTH).blocks[SIZE_X-1][y][z] ; 
	}
	public EBlockType getNorthernBlock(int x, int y, int z){
		return (x<SIZE_X-1) ? blocks[x+1][y][z] : this.getNeighbour(EDirection.NORTH).blocks[0][y][z] ; 
	}
	public EBlockType getWesternBlock(int x, int y, int z){
		return (z>0) ? blocks[x][y][z-1] : this.getNeighbour(EDirection.WEST).blocks[x][y][SIZE_Z-1] ; 
	}
	public EBlockType getEasternBlock(int x, int y, int z){
		return (z<SIZE_Z-1) ? blocks[x][y][z+1] : this.getNeighbour(EDirection.EAST).blocks[x][y][0] ; 
	}
	public EBlockType getUpperBlock(int x, int y, int z){
		return (y<SIZE_Y-1) ? blocks[x][y+1][z] : EBlockType.AIR ; 
	}
	public EBlockType getBottomBlock(int x, int y, int z){
		return (y>0) ? blocks[x][y-1][z] : EBlockType.AIR ; 
	}
	public EBlockType getSouthernBlock(ID_3D blockID){
		return this.getSouthernBlock(blockID.getX(), blockID.getY(), blockID.getZ()); 
	}
	public EBlockType getNorthernBlock(ID_3D blockID){
		return this.getNorthernBlock(blockID.getX(), blockID.getY(), blockID.getZ()); 
	}
	public EBlockType getWesternBlock(ID_3D blockID){
		return this.getWesternBlock(blockID.getX(), blockID.getY(), blockID.getZ()); 
	}
	public EBlockType getEasternBlock(ID_3D blockID){
		return this.getEasternBlock(blockID.getX(), blockID.getY(), blockID.getZ()); 
	}
	public EBlockType getUpperBlock(ID_3D blockID){
		return this.getUpperBlock(blockID.getX(), blockID.getY(), blockID.getZ()); 
	}
	public EBlockType getBottomBlock(ID_3D blockID){
		return this.getBottomBlock(blockID.getX(), blockID.getY(), blockID.getZ()); 
	}
	
	boolean isSurroundedByBlocks(int x, int y, int z){
		return !this.isAir(this.getUpperBlock(x, y, z)) && 
				!this.isAir(this.getBottomBlock(x, y, z)) && 
				!this.isAir(this.getSouthernBlock(x, y, z)) && 
				!this.isAir(this.getNorthernBlock(x, y, z)) && 
				!this.isAir(this.getWesternBlock(x, y, z)) && 
				!this.isAir(this.getEasternBlock(x, y, z)) ; 
		
	}
	public boolean isAir(EBlockType bType){
		return bType == EBlockType.AIR ; 
	}
	
	int getAltitudeDiff(int x, int y, int z){
		int ySouth = y - getHeightValue(this, x-1, z); 
		int yNorth = y - getHeightValue(this, x+1, z); 
		int yWest = y - getHeightValue(this, x, z-1);
		int yEast = y - getHeightValue(this, x, z+1); 
		int diff = 0 ;
		if(ySouth>1 && ySouth > diff) diff = ySouth; 
		if(yNorth>1 && yNorth>diff) diff = yNorth ;
		if(yWest>1 && yWest>diff) diff = yWest ; 
		if(yEast>1 && yEast>diff) diff = yEast ; 
		return diff ; 
	}
	
	Vertex3f getOffset() {
		return this.renderingOffset;
	}

	void setOffset(Vertex3f newOffset) {
		this.renderingOffset = newOffset;
	}

	void changeOffsetByValue(Vertex3f value) {
		this.renderingOffset.setX(this.renderingOffset.getX() + value.getX());
		this.renderingOffset.setY(this.renderingOffset.getY() + value.getY());
		this.renderingOffset.setZ(this.renderingOffset.getZ() + value.getZ());
	}

	void reRender() {
		this.loaded = false;
		glDeleteLists(list, 1);
	}

	void render() {
		if (!loaded) {
			int _y = 0 ; 
			list = glGenLists(1);
			glNewList(list, GL_COMPILE_AND_EXECUTE);
			glPushMatrix();
			glTranslatef(id.getX() * SIZE_X * VOXEL_SIZE, 0, id.getZ() * SIZE_Z
					* VOXEL_SIZE);
			// float r = random.nextFloat();
//			renderChunkGrid(); 
			for (int x = 0; x < SIZE_X; x++) {
				for (int z = 0; z < SIZE_Z; z++) {
					_y = getHeightValue(this, x, z);
					initBlockRendering(_y, x, z);
//					this.renderVoxel(x, _y, z); 
				}
			}
			glPopMatrix();
			glEndList();
			loaded = true;
		} else
			glCallList(list);
	}

	private void renderChunkGrid() {
		glEnable(GL_BLEND); 
		 glColor4f(0,0,1,0.25f);
		glBegin(GL_LINE_LOOP);
			glVertex3f(this.renderingOffset.getX(),SIZE_Y/2,this.renderingOffset.getZ());
			glVertex3f(this.renderingOffset.getX(),SIZE_Y/2,SIZE_Z*VOXEL_SIZE+this.renderingOffset.getZ());
			glVertex3f(SIZE_X*VOXEL_SIZE+this.renderingOffset.getX(),SIZE_Y/2,SIZE_Z*VOXEL_SIZE+this.renderingOffset.getZ());
			glVertex3f(SIZE_X*VOXEL_SIZE+this.renderingOffset.getX(),SIZE_Y/2,this.renderingOffset.getZ());
		glEnd(); 
		glDisable(GL_BLEND);
	}
	
	private void renderVoxel(int x, int y, int z){
		glBegin(GL_POINTS); 
			glVertex3f(x*VOXEL_SIZE+this.renderingOffset.getX(),
					y*VOXEL_SIZE+this.renderingOffset.getY(),
					z*VOXEL_SIZE+this.renderingOffset.getZ()); 
		glEnd(); 
	}

	private void initBlockRendering(int _y, int x, int z) {
		if (!this.isSurroundedByBlocks(x, _y, z)){
			for(int y=_y-this.getAltitudeDiff(x, _y, z); y<=_y; y++)
				this.renderBlock(x, y, z);
		}
	}
	
	private static final float[][] BLOCK_RENDERING_MATRIX = {{0,1,0,1,1,0,1,1,1,0,1,1}, // top
															   {0,0,0,0,0,1,1,0,1,1,0,0}, // bottom
															   {0,0,0,0,1,0,0,1,1,0,0,1}, // south 
															   {1,0,0,1,0,1,1,1,1,1,1,0}, // north
															   {0,0,0,1,0,0,1,1,0,0,1,0}, // west
															   {0,0,1,0,1,1,1,1,1,1,0,1} // east 
															  } ; 
															   
	private EBlockType getNeighbourBlockByDirID(int id, int x, int y, int z){
		switch(id){
		case 0: return this.getUpperBlock(x, y, z);
		case 1: return this.getBottomBlock(x, y, z); 
		case 2: return this.getSouthernBlock(x, y, z);  
		case 3: return this.getNorthernBlock(x, y, z);
		case 4: return this.getWesternBlock(x, y, z); 
		case 5: return this.getEasternBlock(x, y, z); 
		}
		return EBlockType.AIR ; 
	}
	
	private void renderBlock(int x, int y, int z){ 
		glBegin(GL_QUADS);		
			ArrayList<TexUV[]> tex = TextureManager.getTexCoordsByBlockType(this.getBlockByID(new ID_3D(x,y,z))); 
			for(int i=0; i<BLOCK_RENDERING_MATRIX.length; i++){
				if(this.isAir(this.getNeighbourBlockByDirID(i, x, y, z))){
					for(int j=0; j<BLOCK_RENDERING_MATRIX[i].length; j+=3){
						if(!(tex == null))
							glTexCoord2f(tex.get(i)[j/3].getU(), tex.get(i)[j/3].getV());
						glVertex3f((BLOCK_RENDERING_MATRIX[i][j]+x)*VOXEL_SIZE+this.renderingOffset.getX(),
								(BLOCK_RENDERING_MATRIX[i][j+1]+y)*VOXEL_SIZE+this.renderingOffset.getY(),
								(BLOCK_RENDERING_MATRIX[i][j+2]+z)*VOXEL_SIZE+this.renderingOffset.getZ());
					}
				}
			}
		glEnd(); 
	}

}
